/* ******************************************************************************
 * Conf.java
 * 创建日期 2011-4-5
 * Copyright 2011 kingter company, Inc. All rights reserved.
 * kingter PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *******************************************************************************/
package com.ball.tools.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;

import javax.crypto.Cipher;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.alibaba.druid.pool.DruidDataSource;

/**
 * <p>标题: Conf</p>
 * <p>版权: Copyright (c) 2011</p>
 * <p>公司: xxx</p>
 * <p>描述: 读取和解析配置文件</p>
 * 
 * @version 1.0
 * @author xieyicai
 */
public class Conf {
	/** 日志记录器 */
	public static final Logger LOG = LoggerFactory.getLogger(Conf.class);
	/** 配置对象的缓存容器 */
	public static final HashMap<String,Conf> CACHE=new HashMap<String,Conf>();
	/** 配置文件 */
	protected File confFile;
	/** XML文档对象 */
	protected Document document = null;
	/** XML文档的根元素 */
	protected Element root;
	/** 存储数据源 */
	protected HashMap<String,DruidDataSource> dataSourceMap=new HashMap<String,DruidDataSource>();
	/** 项目的根目录 */
	protected String projectPath;
	/** 源代码的包名 */
	protected String packageName;

	/**
	 * 创建配置信息对象
	 * @param confFile  配置文件
	 * @throws Exception
	 */
	private Conf(File confFile) throws Exception {
		setConfFile(confFile);
	}
	/**
	 * 获取对象实例
	 * @param confFile  配置文件
	 * @return 配置信息对象
	 * @throws Exception
	 */
	public synchronized static Conf getInstance(File confFile) throws Exception{
		Conf conf = CACHE.get(getCacheKey(confFile));
		if(conf==null){
			conf=new Conf(confFile);
		}
		return conf;
	}
	/**
	 * 获取配置文件。
	 * @return 配置文件
	 */
	public File getConfFile() {
		return confFile;
	}
	/**
	 * 设置配置文件。<br/>
	 * 该方法会重载所有的配置信息。
	 * @param confFile 配置文件
	 * @throws ParserConfigurationException 
	 * @throws IOException 
	 * @throws SAXException 
	 */
	public void setConfFile(File confFile) throws ParserConfigurationException, SAXException, IOException {
		close();
		document = null;
		root = null;
		projectPath = null;
		packageName = null;
		this.confFile = confFile;
		String suffix="|"+confFile.getCanonicalPath();
		for(String key : CACHE.keySet()){
			if(key.endsWith(suffix)){
				CACHE.remove(key);
			}
		}
		CACHE.put(getCacheKey(confFile), this);
		DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
		factory.setIgnoringElementContentWhitespace(true);
		DocumentBuilder db=factory.newDocumentBuilder();
		document=db.parse(confFile);
		root=document.getDocumentElement();
		loadDataSource();
		loadParameters();
	}
	/**
	 * 从配置文档中加载一些参数信息。
	 */
	private void loadParameters(){
		NodeList list=root.getElementsByTagName("project");
		if(list.getLength()>0){
			Element project=(Element)list.item(0);
			projectPath=project.getAttribute("path");
			packageName=project.getAttribute("packageName");
		}
	}
	/**
	 * 获取项目的根目录
	 * @return 项目的根目录
	 */
	public String getProjectPath() {
		return projectPath;
	}
	/**
	 * 设置项目的根目录
	 * @param projectPath 项目的根目录
	 */
	public void setProjectPath(String projectPath) {
		this.projectPath = projectPath;
	}
	/**
	 * 获取源代码的包名前缀
	 * @return 源代码的包名前缀
	 */
	public String getPackageName() {
		return packageName;
	}
	/**
	 * 设置源代码的包名前缀
	 * @param packageName 源代码的包名前缀
	 */
	public void setPackageName(String packageName) {
		this.packageName = packageName;
	}
	/**
	 * 获取所有的数据源
	 * @return 数据源容器
	 */
	public HashMap<String, DruidDataSource> getDataSourceMap() {
		return dataSourceMap;
	}
	/**
	 * 获取文档对象
	 * @return 文档对象
	 */
	public Document getDocument() {
		return document;
	}
	/**
	 * 获取调试值
	 * @return 调试值
	 */
	public int getDebugValue(){
		int value=0;
		String debug=root.getAttribute("debug");
		if(debug==null || debug.length()==0){
			value="xieyicai".equals(System.getProperty("user.name"))?1:0;
		}else{
			value=Integer.parseInt(debug);
		}
		return value;
	}
	/**
	 * 加载数据源配置
	 */
	private void loadDataSource(){
		Element element;
		NodeList list=root.getElementsByTagName("resource");
		for(int k=0; k<list.getLength(); k++){
			element=(Element)list.item(k);
			String name=element.getAttribute("name");
			int maxActive=Integer.parseInt(element.getAttribute("maxActive"));
			int maxIdle=Integer.parseInt(element.getAttribute("maxIdle"));
			int minIdle=Integer.parseInt(element.getAttribute("minIdle"));
			int maxWait=Integer.parseInt(element.getAttribute("maxWait"));
			String validationQuery=element.getAttribute("validationQuery");
			String username=element.getAttribute("username");
			String password=d(element.getAttribute("password"));
			String driverClassName=element.getAttribute("driverClassName");
			String url=element.getAttribute("url");
			addDataSource(name, maxActive, driverClassName, maxIdle, minIdle, maxWait, url, username, password, validationQuery);
		}
	}
	/**
	 * 向容器里添加一个数据源
	 * @param name		数据源名称
	 * @param maxActive	最多允许几个活动的数据库连接
	 * @param driverClassName	驱动程序的类名
	 * @param maxIdle	最多允许几个空闲的数据库连接
	 * @param minIdle	最少要保留几个空闲的数据库连接
	 * @param maxWait	最长的等待时间，单位：毫秒
	 * @param url		数据库连接地址
	 * @param username	登录数据库的用户名
	 * @param password	登录数据库的密码
	 * @param validationQuery	验证连接的查询语句
	 */
	public synchronized void addDataSource(String name, int maxActive, String driverClassName, int maxIdle, int minIdle, int maxWait, String url, String username, String password, String validationQuery) {
		int timeoutSecond=maxWait/1000;
		DruidDataSource ds=new DruidDataSource();
		ds.setDriverClassName(driverClassName);
		ds.setUrl(url);
		ds.setUsername(username);
		ds.setPassword(password);
		ds.setMaxActive(maxActive);
		ds.setLoginTimeout(timeoutSecond);
		ds.setMinIdle(minIdle);
		ds.setMaxWait(maxWait);
		ds.setValidationQuery(validationQuery);
		ds.setValidationQueryTimeout(timeoutSecond);
		dataSourceMap.put(name, ds);
		//java.sql.DriverManager.setLoginTimeout(timeoutSecond);
	}
	/**
	 * 获取指定名称的数据源
	 * @param name 数据源名称
	 * @return 数据源
	 */
	public DruidDataSource getDataSource(String name){
		return dataSourceMap.get(name);
	}
	/**
	 * 获取默认的数据源
	 * @return 默认的数据源
	 */
	public DruidDataSource getDefaultDataSource(){
		Iterator<DruidDataSource> it=dataSourceMap.values().iterator();
		if(it.hasNext()) {
			return it.next();
		}
		return null;
	}
	/**
	 * 关闭并清空所有的数据源。
	 * @return 是否执行成功
	 */
	public synchronized boolean close(){
		boolean success=true;
		for(DruidDataSource ds : dataSourceMap.values()){
			if(ds!=null && !ds.isClosed()){
				ds.close();
			}
		}
		dataSourceMap.clear();
		return success;
	}
	/**
	 * 通过XPath表达式获取单一的一个XML节点
	 * @param xPath	XPath表达式
	 * @param source	开始计算的XML元素
	 * @return 一个XML节点
	 */
	public static Node selectSingleNode(String xPath, Object source) {
		Node result=null;
		XPathFactory xpathFactory=XPathFactory.newInstance();
		XPath xpath=xpathFactory.newXPath();
		try {
			result=(Node) xpath.evaluate(xPath, source, XPathConstants.NODE);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return result;
	}
	/**
	 * 通过XPath表达式获取一个XML节点列表
	 * @param xPath	XPath表达式
	 * @param source	开始计算的XML元素
	 * @return 一个XML节点列表
	 */
	public static NodeList selectNodes(String xPath, Object source) {
		NodeList result=null;
		XPathFactory xpathFactory=XPathFactory.newInstance();
		XPath xpath=xpathFactory.newXPath();
		try {
			result=(NodeList) xpath.evaluate(xPath, source, XPathConstants.NODESET);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return result;
	}
	/**
	 * 解密
	 * @param hex  16进制格式表示的密文
	 * @return  解密后的明文。
	 */
	private static String d(String hex){
		try{
			char[] a=hex.toCharArray();
			byte[] data=new byte[a.length/2];
			int n=0;
			for(int i=0;i<a.length;i++) {
				if(a[i]>='a' && a[i]<='f')
					n=(a[i]-'a'+10)<<4;
				else if(a[i]>='A' && a[i]<='F')
					n=(a[i]-'A'+10)<<4;
				else if(a[i]>='0' && a[i]<='9')
					n=(a[i]-'0')<<4;
				else
					throw new NumberFormatException("出现非法字符“"+a[i]+"”。");
				i=i+1;
				if(a[i]>='a' && a[i]<='f')
					n+=a[i]-'a'+10;
				else if(a[i]>='A' && a[i]<='F')
					n+=a[i]-'A'+10;
				else if(a[i]>='0' && a[i]<='9')
					n+=a[i]-'0';
				else
					throw new NumberFormatException("出现非法字符“"+a[i]+"”。");
				data[i/2]=(byte)n;
			}
			String algorithm="AES";
			Cipher cipher = Cipher.getInstance(algorithm);
			int keySize=128;
			byte[] format = new byte[keySize/8];
			int i=0;
			format[i++]='P';
			format[i++]='a';
			format[i++]='s';
			format[i++]='s';
			format[i++]='w';
			format[i++]='o';
			format[i++]='r';
			format[i++]='D';
			format[i++]='_';
			format[i++]='1';
			javax.crypto.spec.SecretKeySpec key = new javax.crypto.spec.SecretKeySpec(format, algorithm);
			cipher.init(Cipher.DECRYPT_MODE, key);
			return new String(cipher.doFinal(data));
		}catch(Throwable ex){
			ex.printStackTrace();
		}
		return null;
	}
	/**
	 * 加密
	 * @param password  密码
	 * @param src 需要加密的内容
	 * @return 密文
	 */
	private static String e(byte[] password, byte[] src){
		try{
			String algorithm="AES";
			int keySize=128;
			Cipher cipher = Cipher.getInstance(algorithm);// 创建密码器
			//javax.crypto.KeyGenerator kgen = javax.crypto.KeyGenerator.getInstance(algorithm);
			//kgen.init(keySize, new java.security.SecureRandom(password));
			//key=kgen.generateKey();
			byte[] format = new byte[keySize/8];
			for(int k=0; k<format.length && k<password.length; k++){
				format[k]=password[k];
			}
			java.security.Key key = new javax.crypto.spec.SecretKeySpec(format, algorithm);
			cipher.init(Cipher.ENCRYPT_MODE, key);
			byte[] encrypted = cipher.doFinal(src);
			byte[] digits={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
			byte[] chars=new byte[encrypted.length*2];//每个byte用两个字符才能表示，所以字符串的长度是数组长度的两倍
			int n=0;
			for(int i=0;i<encrypted.length;i++)
			{
				n=encrypted[i] & 0xFF;
				chars[i*2]=digits[n/16];
				chars[i*2+1]=digits[n%16];
			}
			return new String(chars);
		}catch(Throwable ex){
			ex.printStackTrace();
		}
		return null;
	}
	/**
	 * 解析时长表达式<br/>
	 * 例如：parseTimeValue(" 3,600 second") == 3600000<br/>
	 * @param s 时长表达式，支持的时间单位有（hour,minute,second），对应的简写是（h,m,s），如果没有带单位，默认是毫秒。
	 * @return 以毫秒为单位的时间长度。
	 */
	public static long parseTimeValue(String s){
		s=s.replace(",", "").replaceAll("\\s", "").toLowerCase();
		double n;
		try{
			if(s.endsWith("hour")){
				n=Double.parseDouble(s.substring(0, s.length()-4))*3600*1000;
			}else if(s.endsWith("minute")){
				n=Double.parseDouble(s.substring(0, s.length()-6))*60*1000;
			}else if(s.endsWith("second")){
				n=Double.parseDouble(s.substring(0, s.length()-6))*1000;
			}else if(s.endsWith("h")){
				n=Double.parseDouble(s.substring(0, s.length()-1))*3600*1000;
			}else if(s.endsWith("m")){
				n=Double.parseDouble(s.substring(0, s.length()-1))*60*1000;
			}else if(s.endsWith("s")){
				n=Double.parseDouble(s.substring(0, s.length()-1))*1000;
			}else{
				n=Double.parseDouble(s);
			}
			if(n<0){
				throw new IllegalArgumentException("时间长度不能为负数（"+n+"）。");
			}
		}catch(NumberFormatException ex){
			throw new IllegalArgumentException("非法的时间表达式（"+s+"），支持的时间单位有（hour,minute,second），对应的简写是（h,m,s），如果没有带单位，默认是毫秒。", ex);
		}
		return Math.round(n);
	}
	/**
	 * 获取配置的索引关键字
	 * @param file  配置文件
	 * @return 索引关键字
	 * @throws IOException
	 */
	public static String getCacheKey(File file) throws IOException{
		return file.lastModified()+"|"+file.getCanonicalPath();
	}
	public static void main(String[] args) {
		try{
			String encoding=java.nio.charset.Charset.defaultCharset().name();
			if(args.length>0){
				encoding=args[0];
			}
			BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
			System.out.print("请输入密码：");
			String pwd=in.readLine();
			System.out.print("请输入原文：");
			String src=in.readLine();
			String hex=e(pwd.getBytes(encoding), src.getBytes(encoding));
			System.out.println("加密后："+hex);
			System.out.println("解密后："+d(hex));
		}catch(Throwable ex){
			ex.printStackTrace();
		}
	}
}
